use crate::{
    game::Player,
    skill::Skill,
    strategy::{Conseq, Shape, Strategy},
};
use power_map::TotalMap;
use rand::prelude::*;
use serde_json::{json, Value};
use std::{
    fmt::Debug,
    io::{self, Read},
};

#[derive(Clone)]
pub struct Agent<S>(Strategy<u8, S>);

impl<S: Skill> Agent<S> {
    pub fn new_rand(shape: &Shape<S>, rng: &mut (impl Rng + ?Sized)) -> Agent<S> {
        let mut agent = Agent(Strategy::from_fn(shape, || rng.gen_range(0..100)));
        agent.normalize(shape);
        agent
    }

    /// 需要确保输入有效
    pub fn new_read(shape: &Shape<S>, rdr: &mut (impl Read + ?Sized)) -> io::Result<Agent<S>> {
        let mut inner = Strategy::from_fn(shape, || 0);
        rdr.read_exact(inner.data_mut())?;
        Ok(Agent(inner))
    }

    /// 更改后需要执行
    fn normalize(&mut self, shape: &Shape<S>) {
        for my in 0..=shape.max_cost() {
            for their in 0..=shape.max_cost() {
                let conseq = self.0.get_cell(shape, my, their);
                let w = || conseq.values().filter_map(Conseq::normal);
                let count = w().count() as u16;
                loop {
                    let sum = w().map(|w| w.get() as u16).sum::<u16>();
                    if sum <= u8::MAX as u16 {
                        break;
                    }
                    let over = ((sum - u8::MAX as u16).div_ceil(count)) as u8;
                    w().for_each(|w| {
                        w.set(w.get().saturating_sub(over));
                    });
                }
            }
        }
    }

    fn get(&self, shape: &Shape<S>, my: usize, their: usize) -> TotalMap<S, Option<u8>> {
        let conseq = self.0.get(shape, my, their);
        let w = conseq.values().filter_map(Conseq::normal).copied();
        let first = u8::MAX - w.sum::<u8>();
        conseq.map(|conseq| match conseq {
            Conseq::First => Some(first),
            Conseq::Normal(&x) => Some(x),
            _ => None,
        })
    }

    pub fn mutate(&mut self, shape: &Shape<S>, rng: &mut (impl Rng + ?Sized)) {
        for w in self.0.data_mut() {
            let x = rng.gen_range(-3..=3);
            *w = w.saturating_add_signed(x);
        }
        self.normalize(shape);
    }

    pub fn blend(&mut self, shape: &Shape<S>, with: &Self, rng: &mut (impl Rng + ?Sized)) {
        for (w1, &w2) in self.0.data_mut().iter_mut().zip(with.0.data()) {
            let range = w2.min(*w1)..=w2.max(*w1);
            *w1 = rng.gen_range(range);
        }
        self.normalize(shape);
    }

    pub fn data(&self) -> &[u8] {
        self.0.data()
    }

    pub fn show(&self, shape: &Shape<S>)
    where
        S: Debug,
    {
        for my in 0..=shape.max_cost() {
            for their in 0..=shape.max_cost() {
                print!("{my} vs {their}:");
                let conseq = self.0.get(shape, my, their);
                for (s, w) in self.get(shape, my, their) {
                    if let Some(w) = w {
                        print!(" {s:?}({w:3})");
                    } else if let Conseq::Bad = conseq[&s] {
                        print!(" {s:?}(BAD)");
                    }
                }
                println!();
            }
        }
    }

    pub fn encode(&self, shape: &Shape<S>) -> Value {
        fn from_iter(iter: impl Iterator<Item = Value>) -> Value {
            Value::Array(iter.collect())
        }
        from_iter((0..=shape.max_cost()).map(|my| {
            from_iter((0..=shape.max_cost()).map(|their| {
                from_iter(
                    self.get(shape, my, their)
                        .values()
                        .map(|w| json!(w.unwrap_or(0))),
                )
            }))
        }))
    }
}

fn choose_by_weight<T>(
    rng: &mut (impl Rng + ?Sized),
    iter: impl Iterator<Item = (T, f64)>,
) -> Option<T> {
    iter.filter(|(_, p)| *p > 0.0)
        .reduce(|(selected, p_acc), (x, p)| {
            let p_acc = p_acc + p;
            let selected = if rng.gen_bool(p / p_acc) { x } else { selected };
            (selected, p_acc)
        })
        .map(|(x, _)| x)
}

impl<R, S> Player<S> for (&Agent<S>, &Shape<S>, &mut R)
where
    R: Rng + ?Sized,
    S: Skill,
{
    fn play(&mut self, my: usize, their: usize) -> S {
        let (agent, shape, rng) = self;
        let iter = agent
            .get(shape, my, their)
            .into_iter()
            .filter_map(|(s, w)| Some((s, w? as f64)));
        choose_by_weight(rng, iter).expect("没有可用的技能")
    }
}
